//------------------------------------------------------------------------------
// <copyright file="WindowsDataJsonImpl.h" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

#pragma once

#ifndef JSON_IMPLEMENTED
#define USE_PLATFORMSTRING
#define JSON_IMPLEMENTED

// Undefine GetObject from Win32 API because it prevents us from calling IJsonValue::GetObject.
#ifdef GetObject
#undef GetObject
#endif

#include "IJsonNode.h"

using namespace Windows::Data::Json;

namespace Microsoft { namespace Internal { namespace GamesTest { namespace Rpc { namespace Server
{
	/// <summary>
	/// IJsonNode implemented with Windows::Data::Json.
	/// </summary>
	class JsonNode: public IJsonNode
	{
	private:
		static const int ConversionError = 0x8000000E;

	private:
		/// <summary>
		/// Underlying implementation (could be JsonObject, JsonValue or JsonArray).
		/// </summary>
		IJsonValue^ m_imp;

	private:
		/// <summary>
		/// Constructor to transfer ownership of internal implementation.
		/// </summary>
		/// <param name="imp">Internal implementation.</param>
		JsonNode(IJsonValue^ imp): m_imp(nullptr)
		{
			m_imp = imp;
		}

	private:
		/// <summary>
		/// Throw out of range exception for a key.
		/// </summary>
		static std::out_of_range OutOfRange(const wchar_t* key)
		{
			std::wstring what(key);
			return std::out_of_range(std::string(what.begin(), what.end()));
		}

		/// <summary>
		/// Throw out of range exception for an array index.
		/// </summary>
		static std::out_of_range OutOfRange(unsigned long index)
		{
			char buffer[16] = {0};
			_ultoa_s(index, buffer, sizeof(buffer), 10);
			return std::out_of_range(std::string(buffer));
		}

	public:
		/// <summary>
		/// Default constructor.
		/// </summary>
		JsonNode(): m_imp(ref new JsonObject()) {}

		/// <summary>
		/// Deep copy constructor.
		/// </summary>
		/// <param name="other">Node to deep-copy.</param>
		JsonNode(const JsonNode& other)
		{
			// Cannot call ::Parse in initializer list, must be called in the body of the c-tor.
			m_imp = JsonObject::Parse(other.m_imp->Stringify());
		}

		/// <summary>
		/// Move constructor.
		/// </summary>
		/// <param name="other">Node to move into this one.</param>
		JsonNode(JsonNode&& other)
		{
			m_imp = other.m_imp;
			other.m_imp = nullptr;
		}

	public:
		/// <summary>
		/// Gets the node type.
		/// </summary>
		/// <returns>Node type.</returns>
		Types GetType() const
		{
			if (m_imp == nullptr)
			{
				return Types::Object;
			}

			switch (m_imp->ValueType)
			{
			default:
			case JsonValueType::Object:
				return Types::Object;
			case JsonValueType::Boolean:
				return Types::Boolean;			
			case JsonValueType::Array:
				return Types::Array;
			case JsonValueType::String:
				return Types::String;
			case JsonValueType::Number:
				return Types::Number;
			}
		}

		/// <summary>
		/// Checks whether a child node exists.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns><c>true</c> if child node exists, <c>false</c> otherwise.</returns>
		bool Exists(const wchar_t* key) const
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
			{
				return m_imp->GetObject()->HasKey(Platform::StringReference(key));
			}

			return false;
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, double value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
			{
				m_imp->GetObject()->SetNamedValue(Platform::StringReference(key), JsonValue::CreateNumberValue(value));
			}
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, bool value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
			{
				m_imp->GetObject()->SetNamedValue(Platform::StringReference(key), JsonValue::CreateBooleanValue(value));
			}
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, int value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
			{
				m_imp->GetObject()->SetNamedValue(Platform::StringReference(key), JsonValue::CreateNumberValue((double)value));
			}
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, JsonString value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
			{
				m_imp->GetObject()->SetNamedValue(
					Platform::StringReference(key),
					JsonValue::CreateStringValue(Platform::StringReference(value, (unsigned int)value.GetLength())));
			}
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, const wchar_t* value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
			{
				m_imp->GetObject()->SetNamedValue(
					Platform::StringReference(key),
					JsonValue::CreateStringValue(Platform::StringReference(value, (unsigned int)wcslen(value))));
			}
		}

		/// <summary>
		/// Sets the value of a child node, creating if necessary.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(const wchar_t* key, const JsonNode& value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
			{
				m_imp->GetObject()->SetNamedValue(
					Platform::StringReference(key),
					JsonValue::Parse(value.m_imp->Stringify()));
			}
		}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(unsigned long index, double value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				m_imp->GetArray()->SetAt((unsigned long)index, JsonValue::CreateNumberValue(value));
			}
			else
			{
				throw OutOfRange(index);
			}
		}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(unsigned long index, bool value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				m_imp->GetArray()->SetAt((unsigned long)index, JsonValue::CreateBooleanValue(value));
			}
			else
			{
				throw OutOfRange(index);
			}
		}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(unsigned long index, int value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				m_imp->GetArray()->SetAt((unsigned long)index, JsonValue::CreateNumberValue(value));
			}
			else
			{
				throw OutOfRange(index);
			}
		}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(unsigned long index, const wchar_t* value)
		{
			SetValue(index, value, (unsigned long)wcslen(value));
		}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		/// <param name="length">Length of child node value to copy.</param>
		void SetValue(unsigned long index, const wchar_t* value, unsigned long length)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				m_imp->GetArray()->SetAt((unsigned int)index, JsonValue::CreateStringValue(Platform::StringReference(value, (unsigned int)length)));
			}
			else
			{
				throw OutOfRange(index);
			}
		}

		/// <summary>
		/// Sets the value of an array item, creating if necessary.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <param name="value">Child node value to copy.</param>
		void SetValue(unsigned long index, const JsonNode& value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				m_imp->GetArray()->SetAt((unsigned int)index, JsonValue::Parse(value.m_imp->Stringify()));
			}
			else
			{
				throw OutOfRange(index);
			}
		}

		/// <summary>
		/// Gets a child node.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns>Child node.</returns>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		JsonNode GetValue(const wchar_t* key)
		{
			try
			{
				if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
				{
					return JsonNode(m_imp->GetObject()->GetNamedValue(Platform::StringReference(key)));
				}
			}
			catch (Platform::Exception^)
			{
			}

			throw OutOfRange(key);
		}

		/// <summary>
		/// Gets an array node, creating if doesn't exist.
		/// </summary>
		/// <param name="key">Array node key.</param>
		/// <returns>Array node.</returns>
		JsonNode GetArrayValue(const wchar_t* key)
		{
			Platform::StringReference keyString(key);

			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
			{					
				if (m_imp->GetObject()->HasKey(keyString))
				{
					try
					{
						return JsonNode(m_imp->GetObject()->GetNamedValue(keyString));
					}
					catch (Platform::Exception^)
					{
					}
				}
				else
				{
					JsonArray^ created = ref new JsonArray();
					m_imp->GetObject()->SetNamedValue(keyString, created);

					return JsonNode(created);
				}
			}			

			throw OutOfRange(key);
		}

		/// <summary>
		/// Gets an array item.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <returns>Array item.</returns>
		/// <remarks>
		/// Throws std::out_of_range if index was not found.
		/// </remarks>
		JsonNode GetValue(unsigned long index)
		{
			try
			{
				if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
				{
					return JsonNode(m_imp->GetArray()->GetAt((unsigned int)index));
				}
			}
			catch (Platform::Exception^)
			{
			}

			throw OutOfRange(index);
		}

		/// <summary>
		/// Gets a child node value as a specific type.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns>Child node value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		double GetNumericValue(const wchar_t* key) const
		{
			try
			{
				if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
				{
					return m_imp->GetObject()->GetNamedNumber(Platform::StringReference(key));
				}
			}
			catch (Platform::Exception^ ex)
			{
				if (ex->HResult == ConversionError)
				{
					// Return default type value on type mismatch.
					return 0.0;
				}
			}

			throw OutOfRange(key);
		}

		/// <summary>
		/// Gets a child node value as a specific type.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns>Child node value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		int GetIntegerValue(const wchar_t* key) const
		{
			try
			{
				if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
				{
					return (int)m_imp->GetObject()->GetNamedNumber(Platform::StringReference(key));
				}
			}
			catch (Platform::Exception^ ex)
			{
				if (ex->HResult == ConversionError)
				{
					// Return default type value on type mismatch.
					return 0;
				}
			}

			throw OutOfRange(key);
		}

		/// <summary>
		/// Gets an array item.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <returns>Array item value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if index was not found.
		/// </remarks>
		double GetNumericValue(unsigned long index) const
		{
			try
			{
				if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
				{
					return m_imp->GetArray()->GetNumberAt((unsigned int)index);
				}
			}
			catch (Platform::Exception^ ex)
			{
				if (ex->HResult == ConversionError)
				{
					// Return default type value on type mismatch.
					return 0.0;
				}
			}

			throw OutOfRange(index);
		}

		/// <summary>
		/// Gets an array item.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <returns>Array item value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if index was not found.
		/// </remarks>
		int GetIntegerValue(unsigned long index) const
		{
			try
			{
				if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
				{
					return (int)m_imp->GetArray()->GetNumberAt((unsigned int)index);
				}
			}
			catch (Platform::Exception^ ex)
			{
				if (ex->HResult == ConversionError)
				{
					// Return default type value on type mismatch.
					return 0;
				}
			}

			throw OutOfRange(index);
		}

		/// <summary>
		/// Gets a child node value as a specific type.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns>Child node value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		bool GetBooleanValue(const wchar_t* key) const
		{
			try
			{
				if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
				{
					return m_imp->GetObject()->GetNamedBoolean(Platform::StringReference(key));
				}
			}
			catch (Platform::Exception^ ex)
			{
				if (ex->HResult == ConversionError)
				{
					// Return default type value on type mismatch.
					return false;
				}
			}

			throw OutOfRange(key);
		}

		/// <summary>
		/// Gets an array item.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <returns>Array item value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if index was not found.
		/// </remarks>
		bool GetBooleanValue(unsigned long index) const
		{
			try
			{
				if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
				{
					return m_imp->GetArray()->GetBooleanAt((unsigned int)index);
				}
			}
			catch (Platform::Exception^ ex)
			{
				if (ex->HResult == ConversionError)
				{
					// Return default type value on type mismatch.
					return false;
				}
			}

			throw OutOfRange(index);
		}

		/// <summary>
		/// Gets a child node value as a specific type.
		/// </summary>
		/// <param name="key">Child node key.</param>
		/// <returns>Child node value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if key was not found.
		/// </remarks>
		JsonString GetStringValue(const wchar_t* key) const
		{
			try
			{
				if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Object)
				{
					return JsonString(m_imp->GetObject()->GetNamedString(Platform::StringReference(key)));
				}
			}
			catch (Platform::Exception^ ex)
			{
				if (ex->HResult == ConversionError)
				{
					// Return default type value on type mismatch.
					return JsonString::EmptyString();
				}
			}

			throw OutOfRange(key);
		}

		/// <summary>
		/// Gets an array item.
		/// </summary>
		/// <param name="index">Array index.</param>
		/// <returns>Array item value.</returns>
		/// <remarks>
		/// Throws std::out_of_range if index was not found.
		/// </remarks>
		JsonString GetStringValue(unsigned long index) const
		{
			try
			{
				if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
				{
					return JsonString(m_imp->GetArray()->GetStringAt((unsigned int)index));
				}
			}
			catch (Platform::Exception^ ex)
			{
				if (ex->HResult == ConversionError)
				{
					// Return default type value on type mismatch.
					return JsonString::EmptyString();
				}
			}

			throw OutOfRange(index);
		}

		/// <summary>
		/// Adds an item to an array.
		/// </summary>
		/// <param name="value">Child node value to copy.</param>
		void Add(const JsonNode& value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				m_imp->GetArray()->Append(JsonValue::Parse(value.m_imp->Stringify()));
			}
		}

		/// <summary>
		/// Adds an item to an array.
		/// </summary>
		/// <param name="value">Child node value to copy.</param>
		void Add(double value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				m_imp->GetArray()->Append(JsonValue::CreateNumberValue(value));
			}
		}

		/// <summary>
		/// Adds an item to an array.
		/// </summary>
		/// <param name="value">Child node value to copy.</param>
		void Add(int value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				m_imp->GetArray()->Append(JsonValue::CreateNumberValue((double)value));
			}
		}

		/// <summary>
		/// Adds an item to an array.
		/// </summary>
		/// <param name="value">Child node value to copy.</param>
		void Add(bool value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				m_imp->GetArray()->Append(JsonValue::CreateBooleanValue(value));
			}
		}

		/// <summary>
		/// Adds an item to an array.
		/// </summary>
		/// <param name="value">Child node value to copy.</param>
		void Add(const wchar_t* value)
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				m_imp->GetArray()->Append(JsonValue::CreateStringValue(Platform::StringReference(value)));
			}
		}

		/// <summary>
		/// Check whether object or array are empty.
		/// </summary>
		/// <returns><c>true</c> if object or array is empty, <c>false</c> otherwise.</returns>
		bool Empty() const
		{
			if (m_imp != nullptr)
			{
				if (m_imp->ValueType == JsonValueType::Array)
				{
					return m_imp->GetArray()->Size == 0;
				}
				else if (m_imp->ValueType == JsonValueType::Object)
				{
					return m_imp->GetObject()->Size == 0;
				}
			}

			return true;
		}

		/// <summary>
		/// Gets array item count.
		/// </summary>
		/// <returns>Array item count if node is an array.</returns>
		unsigned long GetCount() const
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				return (unsigned long)m_imp->GetArray()->Size;
			}

			return 0;
		}

		/// <summary>
		/// Clears an array.
		/// </summary>
		void Clear()
		{
			if (m_imp != nullptr && m_imp->ValueType == JsonValueType::Array)
			{
				return m_imp->GetArray()->Clear();
			}
		}

		/// <summary>
		/// Serializes to string.
		/// </summary>
		/// <returns>String buffer the node was serialized to.</returns>
		JsonString Serialize() const
		{
			if (m_imp != nullptr)
			{
				return JsonString(m_imp->Stringify());
			}
			else
			{
				return JsonString::EmptyString();
			}
		}

		/// <summary>
		/// Parses from string.
		/// </summary>
		/// <param name="string">String to parse from.</param>
		/// <remarks>
		/// Throws std::invalid_argument if parsing fails.
		/// </remarks>
		void Parse(JsonString string)
		{
			try
			{
				m_imp = JsonObject::Parse(string);
			}
			catch (Platform::Exception^)
			{
				throw std::invalid_argument("string parse error");
			}
		}

	public:
		/// <summary>
		/// Deep copy assignment.
		/// </summary>
		/// <param name="other">Another node to copy.</param>
		/// <returns>Self reference.</returns>
		JsonNode& operator=(const JsonNode& other)
		{
			m_imp = JsonObject::Parse(other.m_imp->Stringify());
			return *this;
		}

		/// <summary>
		/// Move assignment.
		/// </summary>
		/// <param name="other">Another node to move.</param>
		/// <returns>Self reference.</returns>
		JsonNode& operator=(JsonNode&& other)
		{
			m_imp = other.m_imp;
			other.m_imp = nullptr;
			return *this;
		}
	};

}}}}}

#endif // JSON_IMPLEMENTED